#include "Sharemem.hh"

void outFrameInfo( FRAME_HEAD frame )
{
    logInfo("time:%ld,lastFrame:%4ld,nextFrame:%4ld,keyFrame:%s,frameNum:%d,lastKeyFrame:%4ld,nextKeyFrame:%4ld,mediaLen:%d\n",
            frame.tv,frame.lastFrame,frame.nextFrame,(frame.keyFrame)?"y":"n",frame.frameNum,frame.lastKeyFrame,frame.nextFrame,frame.mediaLen);
}

Sharemem::Sharemem( int shareId, int64_t memSize )
: createMem(true), shareKeyId(shareId)
{
    do{
        shmKey = ftok( fname, shareKeyId );
        if( shmKey < 0 ){
            logError( "ftok : %s\n", strerror(errno) );
            break;
        }

        shmId = shmget( shmKey, memSize, IPC_CREAT | IPC_EXCL | 0666 );
        if( shmId < 0 ){
            logError( "Shmget : %s\n", strerror(errno) );
            break;
        }

        shareMemAdd = (char *)shmat( shmId, NULL, 0 );
        if( (long)shareMemAdd < 0 ){
            logError( "Shmget : %s\n", strerror(errno) );
            break;
        }

#ifdef RWLOCK
        rwlock = &(D_MEM_HEAD->mutex);
        pthread_rwlock_init( rwlock, NULL );
        pthread_rwlock_wrlock( rwlock );
#endif
        D_MEM_HEAD->writePtr = sizeof(MEM_HEAD);             
        D_MEM_HEAD->memSize = memSize;              
        for( int i=0; i<MAX_CHN_NUM; i++ ){
            D_MEM_HEAD->readPtr[i] = sizeof(MEM_HEAD); 
        }
        /* logDebug("=======>writePtr %ld, %ld\n", D_MEM_HEAD->writePtr, sizeof(MEM_HEAD) ); */
#ifdef RWLOCK
        pthread_rwlock_unlock( rwlock );
#endif

    }while(0);
}

Sharemem::Sharemem( int shareId, int chnNum )
: createMem(false), shareKeyId(shareId), readChnNum(chnNum) 
{
    do{
        shmKey = ftok( fname, shareKeyId );
        if( shmKey < 0 ){
            logError( "ftok : %s\n", strerror(errno) );
            break;
        }

        shmId = shmget( shmKey, 0, IPC_CREAT );
        if( shmId < 0 ){
            logError( "Shmget : %s\n", strerror(errno) );
            break;
        }

        shareMemAdd = (char *)shmat( shmId, NULL, 0 );
        if( (long)shareMemAdd < 0 ){
            logError( "Shmget : %s\n", strerror(errno) );
            break;
        }

#ifdef RWLOCK
        rwlock = &(D_MEM_HEAD->mutex);
#endif

    }while(0);
}

Sharemem::~Sharemem() {
#ifdef RWLOCK
    if( createMem && shmId > 0 ){
        pthread_rwlock_destroy( &(D_MEM_HEAD->mutex) );
    }
#endif

    if( shareMemAdd != NULL ){
        shmdt( shareMemAdd );
        shareMemAdd = NULL;
    }

    if( createMem && shmId > 0 ){
        if(shmctl(shmId,IPC_RMID,NULL) < 0)
        {
            logError( "shmctl : %s\n", strerror(errno) );
        }
    }

    rwlock = NULL;

}

Sharemem* Sharemem::createNew(int shareId, int64_t memSize ){
    return new Sharemem( shareId, memSize );
}

Sharemem* Sharemem::createNew(int shareId, int chnNum ){
    return new Sharemem( shareId, chnNum );
}

bool Sharemem::checkAdd(){
    return (shareMemAdd != NULL && shmId > 0 )?true:false;
}

int64_t Sharemem::getMinReadPtr(){
    long long minPtr = 0;

    for( int i=0;  i<MAX_CHN_NUM; i++ ){
        minPtr = D_MIN( minPtr, D_MEM_HEAD->readPtr[i] );
    }

    return minPtr;
}

int64_t Sharemem::getFreeSize(){
    return ( D_MEM_HEAD->writePtr >= this->getMinReadPtr() ) ?
        ( D_MEM_HEAD->memSize - D_MEM_HEAD->writePtr ):( this->getMinReadPtr() - D_MEM_HEAD->writePtr );
}

void Sharemem::writePtrMoveToHead( void ){
    int64_t writePtrTmp = D_MEM_HEAD->writePtr;
    /* logDebug("=======>writePtr %ld\n", D_MEM_HEAD->writePtr ); */
    D_MEM_HEAD->writePtr = sizeof(MEM_HEAD);
    /* logDebug("=======>writePtr %ld, %ld\n", D_MEM_HEAD->writePtr, sizeof(MEM_HEAD) ); */
    for( int chn=0; chn< MAX_CHN_NUM; chn++ )
    {
        if( D_MEM_HEAD->readPtr[chn] == writePtrTmp )
        {
            D_MEM_HEAD->readPtr[chn] = D_MEM_HEAD->writePtr;
            /* logDebug("writePtr %ld, readPtr[%d] %ld\n", D_MEM_HEAD->writePtr, chn, D_MEM_HEAD->readPtr[chn] ); */
        }
    }
}

void Sharemem::readPtrMoveToNextFrame( void ){
    FRAME_HEAD *minFrame = D_FRAME_HEAD( this->getMinReadPtr() );
    long long nextFrameAdd = minFrame->nextFrame;

    for( int i=0; i<MAX_CHN_NUM; i++ ){
        if( D_MEM_HEAD->writePtr > nextFrameAdd > D_MEM_HEAD->readPtr[i] ){
            D_MEM_HEAD->readPtr[i] = nextFrameAdd;
        }else if( ( D_MEM_HEAD->writePtr < nextFrameAdd ) &&
                  ( D_MEM_HEAD->writePtr < D_MEM_HEAD->readPtr[i] < nextFrameAdd )
                ){
            D_MEM_HEAD->readPtr[i] = nextFrameAdd;
        }
    }
}

void Sharemem::readPtrMoveToNextKeyFrame( void ){
    FRAME_HEAD *minFrame = D_FRAME_HEAD( this->getMinReadPtr() );
    long long nextFrameAdd = minFrame->nextKeyFrame;

    for( int i=0; i<MAX_CHN_NUM; i++ ){
        if( D_MEM_HEAD->writePtr > nextFrameAdd > D_MEM_HEAD->readPtr[i] ){
            D_MEM_HEAD->readPtr[i] = nextFrameAdd;
        }else if( ( D_MEM_HEAD->writePtr < nextFrameAdd ) &&
                  ( D_MEM_HEAD->writePtr < D_MEM_HEAD->readPtr[i] < nextFrameAdd )
                ){
            D_MEM_HEAD->readPtr[i] = nextFrameAdd;
        }
    }
}

void Sharemem::moveReadPtr(){
#if 0
    /*over writePtr to next keyFrame*/
        this->readPtrMoveToNextKeyFrame();
#else
    /*over writePtr to next Frame*/
        this->readPtrMoveToNextFrame();
#endif
}

void Sharemem::overFlowCheckAndHandle( int32_t needlen ){
    while( this->getFreeSize() < needlen )
    {
        if( D_MEM_HEAD->writePtr > this->getMinReadPtr()  )
        {
            /* logDebug("\n"); */
            this->writePtrMoveToHead();
            if( this->getMinReadPtr() > (long long)sizeof(MEM_HEAD) ){
                continue;
            }
        }

        moveReadPtr();
    }
}

void Sharemem::getMediaInfo( const char *data, PAYLOAD_TYPE_E &mediaType, bool &keyFrame )
{
    if( !data  ){
#ifdef SHM_DEBUG
        logInfo("medio date not is NULL!!!\n");
#endif
    }

    mediaType = PT_BUTT;
    keyFrame = false;
}

int Sharemem::PushMedia( char *data, int len,struct timeval tvl )
{
    if( tvl.tv_sec == 0 )
    {
        gettimeofday( &tvl, NULL );
    }
    int64_t tv_ms = (int64_t)tvl.tv_sec * 1000 + (int64_t)tvl.tv_usec / 1000;
    /* logDebug("%ld\n", tv_ms); */
    return this->PushMedia( data, len, tv_ms );
}

int Sharemem::PushMedia( char *data, int len, int64_t tv_ms, PAYLOAD_TYPE_E mediaType )
{
    int ret = SHM_SUCCESS;
    do{
        if( checkAdd() != true ){
            ret = SHM_FAIL;
#ifdef SHM_DEBUG
            logError("Illegal address!!!\n");
#endif
            break;
        }
        if( !data  ){
#ifdef SHM_DEBUG
            logError("medio date not is NULL!!!\n");
#endif
            ret = SHM_PARAM;
            break;
        }

#ifdef RWLOCK
        pthread_rwlock_wrlock( rwlock );
#endif
        if( D_MEM_HEAD->writePtr < (long long)sizeof(MEM_HEAD) ||
            D_MEM_HEAD->writePtr > D_MEM_HEAD->memSize ){
            D_MEM_HEAD->writePtr = (long long)sizeof(MEM_HEAD);
        }
        /* step 1 : check writePtr availability */
        overFlowCheckAndHandle( len );

        /* step 2 : check and update media info */
        /* logDebug("writePtr %ld\n", D_MEM_HEAD->writePtr ); */
        FRAME_HEAD *mediaFrame = D_FRAME_HEAD( D_MEM_HEAD->writePtr ); 
        FRAME_HEAD *mediaLastFrame = D_FRAME_HEAD( D_MEM_HEAD->lastWritePtr );
        mediaFrame->tv = tv_ms;

        mediaFrame->lastFrame = D_MEM_HEAD->lastWritePtr;
        mediaFrame->nextFrame = 0;
        if( mediaType == PT_BUTT )
        {
            this->getMediaInfo( data, mediaFrame->mediaType, mediaFrame->keyFrame );
        }else
        {
            mediaFrame->mediaType = mediaType;
            this->getMediaInfo( data, mediaType, mediaFrame->keyFrame );
        }
        mediaFrame->frameNum  = ( mediaFrame->keyFrame == true )? 0:(mediaLastFrame->frameNum+1);
        mediaFrame->lastKeyFrame = ( mediaLastFrame->keyFrame == true ) ? \
                                   (mediaFrame->lastFrame):(mediaLastFrame->keyFrame);
        mediaFrame->mediaLen = len;
        memcpy( mediaFrame->mediaData, data, len );

        /* step 3 : update last frame info */
        mediaLastFrame->nextFrame = D_MEM_HEAD->writePtr;
        if( mediaFrame->keyFrame == true ){
            D_FRAME_HEAD( mediaFrame->lastKeyFrame )->nextKeyFrame = D_MEM_HEAD->writePtr;
            D_FRAME_HEAD( mediaFrame->lastKeyFrame )->EndFrame = true;
        }else
        {
            D_FRAME_HEAD( mediaFrame->lastKeyFrame )->EndFrame = false;
        }

        /* step 4 : update shared memory head info */ 
        D_MEM_HEAD->lastWritePtr = D_MEM_HEAD->writePtr;
        D_MEM_HEAD->writePtr += sizeof(FRAME_HEAD) + len;

        /* outFrameInfo( *mediaFrame ); */

#ifdef RWLOCK
        pthread_rwlock_unlock( rwlock );
#endif
    }while(0);

    return ret;
}

int Sharemem::GetMedia( int chn, FRAME_INFO &media_info )
{
    int ret = SHM_SUCCESS;
    do{
        if( chn >= MAX_CHN_NUM ){
#ifdef SHM_DEBUG
            logInfo("Channel number is not in valid range( 0 <= chn < %d )!!!\n", MAX_CHN_NUM);
#endif
            ret = SHM_PARAM;
            break;
        }

        struct shmid_ds buf;
        if(shmctl(shmId,IPC_STAT,&buf) < 0 )
        {
#ifdef SHM_DEBUG
            logError( "shmctl : %s\n", strerror(errno) );
#endif
            ret = SHM_RMID;
            break;
        }

#ifdef RWLOCK
        pthread_rwlock_wrlock( rwlock );
#endif

        if( D_MEM_HEAD->readPtr[chn] < (long long)sizeof(MEM_HEAD) ||
            D_MEM_HEAD->readPtr[chn] > D_MEM_HEAD->memSize ){
            D_MEM_HEAD->readPtr[chn] = (long long)sizeof(MEM_HEAD);
        }

        FRAME_HEAD *mediaFrame = D_FRAME_HEAD( D_MEM_HEAD->readPtr[chn] );
        if( D_MEM_HEAD->readPtr[chn] == D_MEM_HEAD->writePtr ){
            /* logInfo("over flow...\n"); */
            ret = SHM_OVERFLOW;
            break;
        }

        media_info.mediaType = mediaFrame->mediaType;
        media_info.tv = mediaFrame->tv;
        media_info.keyFrame = mediaFrame->keyFrame;
        media_info.frameNum = mediaFrame->frameNum;
        media_info.EndFrame = mediaFrame->EndFrame;
        media_info.mediaLen = mediaFrame->mediaLen;
        media_info.mediaData = (char*)mediaFrame->mediaData;
        /* if( mediaFrame->nextFrame != 0 ){ */
        /*     D_MEM_HEAD->readPtr[chn] = mediaFrame->nextFrame; */
        /* } */

        /* outFrameInfo( *mediaFrame ); */
#ifdef RWLOCK
        pthread_rwlock_unlock( rwlock );
#endif

    }while(0);

    return ret;
}

int Sharemem::ReleaseMedia( int chn )
{
    int ret = SHM_SUCCESS;
    do{
        if( chn >= MAX_CHN_NUM ){
#ifdef SHM_DEBUG
            logInfo("Channel number is not in valid range( 0 <= chn < %d )!!!\n", MAX_CHN_NUM);
#endif
            ret = SHM_PARAM;
            break;
        }
        FRAME_HEAD *mediaFrame = D_FRAME_HEAD( D_MEM_HEAD->readPtr[chn] );
        if( mediaFrame->nextFrame != 0 ){
            /* logDebug("readPtr %ld, next %ld\n", D_MEM_HEAD->readPtr[chn],mediaFrame->nextFrame); */
            D_MEM_HEAD->readPtr[chn] = mediaFrame->nextFrame;
        }else{
            /* logDebug("readPtr %ld, last %ld\n", D_MEM_HEAD->readPtr[chn],D_MEM_HEAD->writePtr); */
            D_MEM_HEAD->readPtr[chn] = D_MEM_HEAD->writePtr;
        }
    }while(0);

    return ret;
}

int Sharemem::PopMedia( int chn, char *data, int &len, int64_t &tv_ms )
{
    int ret = SHM_SUCCESS;
    do{
        FRAME_INFO mediaInfo;
        ret = this->GetMedia( chn, mediaInfo );
        if( ret != SHM_SUCCESS )
        {
            break;
        }
        memcpy( data, mediaInfo.mediaData, mediaInfo.mediaLen );
        len = mediaInfo.mediaLen;
        tv_ms = mediaInfo.tv;

        ret = this->ReleaseMedia(chn);
    }while(0);

    return ret;
}

int Sharemem::PopMedia( int chn, char *data, int &len )
{
    int64_t tv_ms;
    return this->PopMedia( chn, data, len, tv_ms );
}

char* Sharemem::PopMedia( int chn, int &len, int64_t &tv_ms )
{
    char *media = NULL;
    int ret = SHM_SUCCESS;
    do{
        FRAME_INFO mediaInfo;
        ret = this->GetMedia( chn, mediaInfo );
        if( ret != SHM_SUCCESS )
        {
            media = NULL;
            break;
        }
        media = mediaInfo.mediaData;
        len = mediaInfo.mediaLen;
        tv_ms = mediaInfo.tv;

        ret = this->ReleaseMedia(chn);
        if( ret != SHM_SUCCESS )
        {
            media = NULL;
            break;
        }
    }while(0);

    return media;
}

char* Sharemem::PopMedia( int chn, int &len )
{
    int64_t tv_ms;
    return this->PopMedia( chn, len, tv_ms );
}

